package com.adams.distributed.control;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.Callable;

import org.apache.log4j.Logger;

import com.adams.core.exception.BusinessException;
import com.adams.utils.FileUtil;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;

public class SftpForWindows extends Sftp
{
	Logger logger = Logger.getLogger(SftpForWindows.class.getName());

	private JSch jsch;
	private Session session;
	private String SysFont = null;

	private String sourceRootPath = null;
	private String targetRootPath = null;

	private List<String> localFolderPaths = new ArrayList<String>(64);
	private List<String> localFilePaths = new ArrayList<String>(64);
	private List<CopyFileDto> remoteFilePaths = new ArrayList<CopyFileDto>(64);

	public SftpForWindows(String user, String pwd, String host, int port,
			String SysFont) throws BusinessException
	{
		try
		{
			jsch = new JSch();
			session = jsch.getSession(user, host, port);
			session.setPassword(pwd);
			session.setConfig("StrictHostKeyChecking", "no");
			session.connect();
		} catch (JSchException e)
		{
			StringBuilder message = new StringBuilder("连接");
			message.append(host);
			message.append("服务器失败!user:").append(user);
			message.append(" pwd:").append(pwd);
			message.append(" port:").append(port);
			logger.error(message.toString(), e);
			throw new BusinessException("ssh.connection.error");
		}
		this.SysFont = SysFont;
	}

	public void disconnect()
	{
		if (null != session)
		{
			session.disconnect();
		}
		if (null != jsch)
		{
			jsch = null;
		}
	}

	@Override
	protected void finalize() throws Throwable
	{
		super.finalize();
		disconnect();
	}

	public boolean put(String src, String dist, boolean cleanBeforeCopy)
	{
		ChannelSftp c = null;
		try
		{
			File srcFolder = new File(src);
			sourceRootPath = changePathSeparator(src);
			targetRootPath = changePathSeparator(dist);
			boolean cleanNotExists = true;
			if (cleanBeforeCopy)
			{
				try
				{
					if (!srcFolder.exists())
					{
						delete(targetRootPath);
					} else if (srcFolder.isDirectory())
					{
						c = getChannel();
						mkdir(c, targetRootPath);
						deleteDistBySrcNotExists(c, src, targetRootPath);
						c.disconnect();
					}
				} catch (Exception e)
				{
					logger.error("删除目标多余文件失败！", e);
					cleanNotExists = false;
				}
			}

			if (srcFolder.isFile())
			{
				c = getChannel();
				String targetFilename = srcFolder.getName();
				String targetDir = targetRootPath.substring(0, targetRootPath
						.length()
						- targetFilename.length() - 1);
				mkdir(c, targetDir);
				syncFile(c, src, targetDir, targetFilename);
				c.disconnect();
			} else if (srcFolder.exists())
			{
				readFolder(srcFolder);
				if (mkFolder())
				{
					readFile(srcFolder);
					mkFile();
				}
			}

			if (!cleanNotExists)
			{
				return false;
			}
		} catch (Throwable e)
		{
			logger.error("文件上传失败！", e);
			return false;
		} finally
		{
			localFolderPaths.clear();
			if (null != c)
			{
				c.disconnect();
			}
		}

		return true;
	}

	@SuppressWarnings("unchecked")
	private void deleteDistBySrcNotExists(ChannelSftp c, String src, String dist)
			throws SftpException, JSchException, IOException
	{
		File srcFile = new File(src);
		if (!srcFile.exists())
		{
			delete(dist);
			return;
		}

		Vector vt = c.ls(".");
		if (vt != null)
		{
			if (vt.size() > 2)
			{
				for (int i = 0; i < vt.size(); i++)
				{
					Object obj = vt.elementAt(i);
					String fileName = ((com.jcraft.jsch.ChannelSftp.LsEntry) obj)
							.getFilename();
					SftpATTRS arr = ((com.jcraft.jsch.ChannelSftp.LsEntry) obj)
							.getAttrs();
					if ("..".equals(fileName) || ".".equals(fileName))
					{
						continue;
					}

					String srcPath = FileUtil.addPath(src, fileName);
					srcFile = new File(srcPath);
					if (arr.isDir())
					{
						String deletePath = dist + pathSeparator_str + fileName;
						if (!srcFile.exists())
						{
							delete(deletePath);
						} else
						{
							c.cd(fileName);
							deleteDistBySrcNotExists(c, srcPath, deletePath);
						}
					} else
					{
						if (!srcFile.exists())
						{
							c.rm(fileName);
						}
					}
				}
			}
			c.cd("..");
		}
	}

	private void delete(String deletePath) throws JSchException, IOException
	{
		Channel channel = null;
		InputStream in = null;
		try
		{
			channel = session.openChannel("exec");
			ChannelExec cExec = (ChannelExec) channel;
			StringBuilder command = new StringBuilder("cd ");
			String[] paths = deletePath.split(pathSeparator_str);
			command.append(paths[0]);
			command.append("\n");
			command.append("rm -r -f ");
			command.append(deletePath);
			cExec.setCommand(command.toString());
			in = cExec.getInputStream();
			cExec.connect();
			byte[] tmp = new byte[1024];
			while (true)
			{
				while (in.available() > 0)
				{
					int j = in.read(tmp, 0, 1024);
					if (j < 0)
						break;
					logger.error("删除文件失败！原因：" + new String(tmp, 0, j));
				}
				if (cExec.isClosed())
				{
					break;
				}
			}
		} catch (JSchException e)
		{
			throw e;
		} catch (IOException e)
		{
			throw e;
		} finally
		{
			if (null != channel)
			{
				channel.disconnect();
			}
			if (null != in)
			{
				in.close();
			}
		}
	}

	private void readFolder(File rootFolder)
	{
		if (!rootFolder.exists())
		{
			return;
		}
		File[] childrenFolder = rootFolder.listFiles();
		if (null == childrenFolder)
		{
			return;
		}
		for (int i = 0; i < childrenFolder.length; i++)
		{
			if (childrenFolder[i].isDirectory())
			{
				String targetPath = getTargetPath(childrenFolder[i]
						.getAbsolutePath());
				localFolderPaths.add(targetPath);
				readFolder(childrenFolder[i]);
			}
		}
	}

	private void readFile(File rootFolder)
	{
		if (!rootFolder.exists())
		{
			return;
		}
		File[] childrenFolder = rootFolder.listFiles();
		if (null == childrenFolder)
		{
			return;
		}
		for (int i = 0; i < childrenFolder.length; i++)
		{
			if (!childrenFolder[i].isDirectory())
			{
				localFilePaths.add(childrenFolder[i].getAbsolutePath());
			} else
			{
				readFile(childrenFolder[i]);
			}
		}
	}

	private boolean mkFolder()
	{
		int makeFolderTaskAmount = needMakeFolderTaskAmount(localFolderPaths
				.size());
		try
		{
			int averageFolderNum = localFolderPaths.size()
					/ makeFolderTaskAmount;
			List<SftpMakeFolderCall> makeFolderTasks = new ArrayList<SftpMakeFolderCall>(
					makeFolderTaskAmount);

			for (int i = 0; i < makeFolderTaskAmount; i++)
			{
				List<String> folderPathsToTask = null;
				if (i == makeFolderTaskAmount - 1)
				{
					folderPathsToTask = localFolderPaths.subList(i
							* averageFolderNum, localFolderPaths.size());
				} else
				{
					folderPathsToTask = localFolderPaths.subList(i
							* averageFolderNum, (i + 1) * averageFolderNum);
				}
				SftpMakeFolderCall mkFolderCall = new SftpMakeFolderCall(
						folderPathsToTask);
				makeFolderTasks.add(mkFolderCall);
			}

			filecopyPool.invokeAll((Collection) makeFolderTasks);
		} catch (Exception e)
		{
			logger.error("创建文件夹失败！", e);
			return false;
		}

		return true;
	}

	private void mkFile()
	{
		int makeFileTaskAmount = needMakeFileTaskAmount(localFilePaths.size());
		try
		{
			int averageFileNum = localFilePaths.size() / makeFileTaskAmount;

			List<SftpUploadFileCall> makeFileTasks = new ArrayList<SftpUploadFileCall>(
					makeFileTaskAmount);

			for (int i = 0; i < makeFileTaskAmount; i++)
			{
				List<String> filePathsToTask = null;
				if (i == makeFileTaskAmount - 1)
				{
					filePathsToTask = localFilePaths.subList(
							i * averageFileNum, localFilePaths.size());
				} else
				{
					filePathsToTask = localFilePaths.subList(
							i * averageFileNum, (i + 1) * averageFileNum);
				}
				SftpUploadFileCall mkFileCall = new SftpUploadFileCall(
						filePathsToTask);
				makeFileTasks.add(mkFileCall);
			}

			filecopyPool.invokeAll((Collection) makeFileTasks);
		} catch (Exception e)
		{
			logger.error("复制文件失败！", e);
		}
	}

	private ChannelSftp getChannel() throws Exception
	{
		Channel channel = session.openChannel("sftp");
		channel.connect();
		ChannelSftp c = (ChannelSftp) channel;

		if (!"UTF-8".equalsIgnoreCase(SysFont))
		{
			Field field = c.getClass().getDeclaredField("fEncoding");
			field.setAccessible(true);
			field.set(c, SysFont);
		}
		return c;
	}

	private boolean isFolderExists(ChannelSftp c, String folderName)
			throws Exception
	{
		Vector vt = c.ls(".");

		if (vt != null)
		{
			for (int i = 0; i < vt.size(); i++)
			{
				Object obj = vt.elementAt(i);
				String fileName = ((com.jcraft.jsch.ChannelSftp.LsEntry) obj)
						.getFilename();
				if (fileName.equals(folderName))
				{
					return true;
				}
			}
		}

		return false;
	}

	private String getTargetPath(String sourceFileAbsolutePath)
	{
		StringBuffer targetPath = new StringBuffer(targetRootPath);
		sourceFileAbsolutePath = changePathSeparator(sourceFileAbsolutePath);
		String str = sourceFileAbsolutePath.replaceFirst(sourceRootPath, "");
		targetPath.append(str);
		return targetPath.toString();
	}

	class SftpMakeFolderCall implements Callable
	{

		private List<String> folderPathsToTask;

		public SftpMakeFolderCall(List<String> folderPathsToTask)
		{
			this.folderPathsToTask = folderPathsToTask;
		}

		@Override
		public Object call() throws Exception
		{
			logger.info(Thread.currentThread().getId()
					+ "..start and folder amount is "
					+ folderPathsToTask.size());
			ChannelSftp c = null;
			try
			{
				c = getChannel();
				Iterator<String> it_folderPathsToTask = folderPathsToTask
						.iterator();
				while (it_folderPathsToTask.hasNext())
				{
					String folderPath = it_folderPathsToTask.next();
					folderPath = changePathSeparator(folderPath);
					mkdir(c, folderPath);
				}
			} catch (Exception e)
			{
				throw e;
			} finally
			{
				if (null != c)
				{
					c.disconnect();
				}
			}
			logger.info(Thread.currentThread().getId() + "..finish");

			return null;
		}
	}

	private void mkdir(ChannelSftp c, String folderPath)
			throws NoSuchFieldException, IllegalAccessException, Exception,
			SftpException, InterruptedException
	{
		String[] paths = folderPath.split(pathSeparator_str);

		Field field = c.getClass().getDeclaredField("cwd");
		field.setAccessible(true);
		field.set(c, paths[0]);

		for (int i = 1; i < paths.length; i++)
		{
			if (isFolderExists(c, paths[i]))
			{
				c.cd(paths[i]);
			} else
			{
				// 如果其他线程已经创建改文件夹，当前线程暂停1秒
				try
				{
					c.mkdir(paths[i]);
				} catch (Exception e)
				{
					Thread.currentThread().sleep(200);
				}
				c.cd(paths[i]);
			}
		}
	}

	class SftpUploadFileCall implements Callable
	{

		private List<String> filePathsToTask;

		public SftpUploadFileCall(List<String> filePathsToTask)
		{
			this.filePathsToTask = filePathsToTask;
		}

		@Override
		public Object call() throws Exception
		{

			logger.info(Thread.currentThread().getId()
					+ "..start and file amount is " + filePathsToTask.size());
			ChannelSftp c = null;
			try
			{
				c = getChannel();

				Iterator<String> it_filePathsToTask = filePathsToTask
						.iterator();
				while (it_filePathsToTask.hasNext())
				{
					String srcFilePath = it_filePathsToTask.next();
					String targetPath = getTargetPath(srcFilePath);
					String targetFilename = FileUtil.getFileName(targetPath,
							"/");
					String targetDir = targetPath.substring(0, targetPath
							.length()
							- targetFilename.length() - 1);
					syncFile(c, srcFilePath, targetDir, targetFilename);
				}
			} catch (Exception e)
			{
				throw e;
			} finally
			{
				if (null != c)
				{
					c.disconnect();
				}
			}

			logger.info(Thread.currentThread().getId() + "..finish");

			return null;
		}
	}

	private void syncFile(ChannelSftp c, String srcFilePath, String targetDir,
			String targetFilename) throws NoSuchFieldException,
			IllegalAccessException, SftpException, FileNotFoundException
	{
		File srcFile = new File(srcFilePath);
		int mtime = (int) (srcFile.lastModified() / 1000);
		Field field = c.getClass().getDeclaredField("cwd");
		field.setAccessible(true);
		field.set(c, targetDir);
		boolean syncFlag = true;
		try
		{
			SftpATTRS attrs = c.lstat(targetFilename);
			if (mtime == attrs.getMTime())
			{
				syncFlag = false;
			}
		} catch (Exception e)
		{
		}
		if (syncFlag)
		{
			String targetPath = targetDir + "/" + targetFilename;
			c._put(new FileInputStream(srcFilePath), targetPath, null,
					ChannelSftp.OVERWRITE);
			c.setMtime(targetFilename, mtime);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean get(String src, String dist, boolean cleanBeforeCopy)
	{
		ChannelSftp c = null;
		try
		{
			if (cleanBeforeCopy)
			{
				FileUtil.deleteDir(dist);
			}
			sourceRootPath = changePathSeparator(src);

			String[] paths = sourceRootPath.split(pathSeparator_str);
			c = getChannel();
			Field field = c.getClass().getDeclaredField("cwd");
			field.setAccessible(true);
			field.set(c, paths[0]);

			for (int i = 1; i < paths.length; i++)
			{
				c.cd(paths[i]);
			}
			readEmptyFolderAndFile(c, dist);
			c.disconnect();
			int remoteFilePathsLen = remoteFilePaths.size();
			int taskAmount = needMakeFileTaskAmount(remoteFilePathsLen);
			List<SftpDownFileCall> dowmFileTasks = new ArrayList<SftpDownFileCall>(
					taskAmount);

			int averageFileNum = remoteFilePathsLen / taskAmount;
			for (int i = 0; i < remoteFilePathsLen;)
			{
				int start = i;
				int end = i + averageFileNum;
				if (end > remoteFilePathsLen)
				{
					end = remoteFilePathsLen;
				}
				List<CopyFileDto> subList = remoteFilePaths.subList(start, end);
				SftpDownFileCall sftpDownFileCall = new SftpDownFileCall(
						subList);
				dowmFileTasks.add(sftpDownFileCall);
				i = end;
			}
			try
			{
				filecopyPool.invokeAll((Collection) dowmFileTasks);
			} catch (Exception e)
			{
				throw e;
			}
		} catch (Exception e)
		{
			logger.error("文件下载失败！", e);
			return false;
		} finally
		{
			remoteFilePaths.clear();
			if (null != c)
			{
				c.disconnect();
			}
		}

		return true;
	}

	@SuppressWarnings("unchecked")
	private void readEmptyFolderAndFile(ChannelSftp c, String dist)
			throws Exception
	{
		Vector vt = c.ls(".");

		if (vt != null)
		{
			if (vt.size() > 2)
			{
				for (int i = 0; i < vt.size(); i++)
				{
					Object obj = vt.elementAt(i);
					String fileName = ((com.jcraft.jsch.ChannelSftp.LsEntry) obj)
							.getFilename();
					SftpATTRS arr = ((com.jcraft.jsch.ChannelSftp.LsEntry) obj)
							.getAttrs();

					if (!"..".equals(fileName) && !".".equals(fileName))
					{
						String distPath = FileUtil.addPath(dist, fileName);
						if (arr.isDir())
						{
							FileUtil.createDir(distPath);
							c.cd(fileName);
							readEmptyFolderAndFile(c, distPath);
						} else
						{
							String srcFile = c.pwd() + pathSeparator_str
									+ fileName;
							File distFile = new File(distPath);
							if (distFile.exists())
							{
								int mtime = (int) (distFile.lastModified() / 1000);
								if (arr.getMTime() == mtime)
								{
									continue;
								}
							}
							remoteFilePaths.add(new CopyFileDto(srcFile,
									distPath, ((long) arr.getMTime()) * 1000));
						}
					}
				}
			}
			c.cd("..");
		}

	}

	class CopyFileDto
	{
		String srcFile;
		String distFile;
		long mtime;

		CopyFileDto(String srcFile, String distFile, long mtime)
		{
			this.srcFile = srcFile;
			this.distFile = distFile;
			this.mtime = mtime;
		}
	}

	class SftpDownFileCall implements Callable
	{

		private List<CopyFileDto> downFileDtos;

		public SftpDownFileCall(List<CopyFileDto> downFileDtos)
		{
			this.downFileDtos = downFileDtos;
		}

		private void copyFile(ChannelSftp c, CopyFileDto downFileDto)
				throws Exception
		{
			File file = new File(downFileDto.distFile);
			if (!file.exists())
			{
				file.createNewFile();
			}
			OutputStream fo = new FileOutputStream(file);
			try
			{
				c.get(downFileDto.srcFile, fo);
			} catch (SftpException e)
			{
				throw e;
			} finally
			{
				fo.close();
			}
			file.setLastModified(downFileDto.mtime);
		}

		@Override
		public Object call() throws Exception
		{

			logger.info(Thread.currentThread().getId()
					+ "..start and file amount is " + downFileDtos.size());
			ChannelSftp c = null;
			try
			{
				c = getChannel();

				Iterator<CopyFileDto> it_filePathsToTask = downFileDtos
						.iterator();
				while (it_filePathsToTask.hasNext())
				{
					CopyFileDto downFileDto = it_filePathsToTask.next();
					copyFile(c, downFileDto);
				}
			} catch (Exception e)
			{
				throw e;
			} finally
			{
				if (null != c)
				{
					c.disconnect();
				}
			}

			logger.info(Thread.currentThread().getId() + "..finish");

			return null;
		}
	}

	public static void main(String[] args) throws Exception
	{
		for (int i = 0; i < 5000; i++)
		{
			try
			{
				SftpForWindows test = new SftpForWindows("Administrator",
						"test", "127.0.0.1", 22, "UTF-8");
				Date startDate = new Date();
				boolean success = test.put("E:\\WFToolkit\\",
						"E:\\why\\WFToolkit\\", true);

				// test.get("c:\\why\\adams-all\\adams-bean", "D:\\adams-bean",
				// false);

				Date endDate1 = new Date();
				System.out.println("read success:" + success
						+ " use time(SECONDS):"
						+ (endDate1.getTime() - startDate.getTime()) / 1000);

				test.disconnect();
			} catch (Exception e)
			{
				e.printStackTrace();
			}
		}
	}
}
